////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) Microsoft Corporation.  All rights reserved.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Gadgeteer.Interfaces
{
    using System;
    using Microsoft.SPOT;
    using Microsoft.SPOT.Hardware;
    using Gadgeteer.Modules;
    /// <summary>
    /// Represents interrupt input on a single pin not bound to a specific <see cref="T:Gadgeteer.Socket"/>.
    /// </summary>
    public class InterruptInput
    {
        private InterruptPort port;
        /// <summary>
        /// Gets or sets a value that determines how the <see cref="Interrupt"/> event is raised.
        /// </summary>
        /// <remarks>
        /// An interrupt may occur on a thread other than the application thread. 
        /// When <see cref="SynchronousUnsafeEventInvocation"/> is <b>false</b> (the default),
        /// the <see cref="Interrupt"/> event, which is raised in response to the interrupt on the interface,
        /// is not raised immediately, instead it is queued for raising on the application's dispatcher thread.  However, 
        /// when <see cref="SynchronousUnsafeEventInvocation"/> is <b>true</b>, the 
        /// <see cref="Interrupt"/> event is raised immediately on the same thread that generated the interrupt.  
        /// This results in faster interrupt processing and may be useful to respond to realtime events, but extra care 
        /// must be taken when using this facility to be thread-safe, i.e. to handle issues such as locking, 
        /// atomic reading/writing of streams/files, deadlock avoidance, etc.
        /// </remarks>
        public bool SynchronousUnsafeEventInvocation { get; set; }

        // Note: A constructor summary is auto-generated by the doc builder.
        /// <summary></summary>
        /// <param name="socket">The socket for the interrupt input interface.</param>
        /// <param name="pin">The pin used by the interrupt input interface.</param>
        /// <param name="glitchFilterMode">
        ///  A value from the <see cref="GlitchFilterMode"/> enumeration that specifies 
        ///  whether to enable the glitch filter on this interrupt input interface.
        /// </param>
        /// <param name="resistorMode">
        ///  A value from the <see cref="ResistorMode"/> enumeration that establishes a default state for the interrupt input interface. N.B. .NET Gadgeteer mainboards are only required to support ResistorMode.PullUp on interruptable GPIOs and are never required to support ResistorMode.PullDown; consider putting the resistor on the module itself.
        /// </param>
        /// <param name="interruptMode">
        ///  A value from the <see cref="InterruptMode"/> enumeration that establishes the requisite conditions 
        ///  for the interface port to generate an interrupt.
        /// </param>
        /// <param name="module">The module using this interrupt input interface, which can be null if unspecified.</param>
        public InterruptInput(Socket socket, Socket.Pin pin, GlitchFilterMode glitchFilterMode, ResistorMode resistorMode, InterruptMode interruptMode, Module module)
        {
            this.port = new InterruptPort(socket.ReservePin(pin, module), glitchFilterMode == GlitchFilterMode.On, (Port.ResistorMode)resistorMode, (Port.InterruptMode)interruptMode);

            if (this.port == null)
            {
                // this is a mainboard error but should not happen since we check for this, but it doesnt hurt to double-check
                throw new Socket.InvalidSocketException("Socket " + socket + " has an error with its Interrupt Input functionality. Please try a different socket.");
            }

            this.SynchronousUnsafeEventInvocation = false;
            this.port.OnInterrupt += new NativeEventHandler(this._port_OnInterrupt);
        }


        /// <summary>
        /// Reads a Boolean value at the InterruptInput interface port input. 
        /// </summary>
        /// <returns>A Boolean value that indicates the current value of the port as either 0 or 1).</returns>
        public bool Read()
        {
            return this.port.Read();
        }

        private void _port_OnInterrupt(uint data1, uint data2, DateTime time)
        {
            bool value = data2 > 0 ? true : false;
            this.OnInterruptEvent(this, value);
        }

        private InterruptEventHandler onInterrupt;

        /// <summary>
        /// Raises the <see cref="Interrupt"/> event.
        /// </summary>
        /// <param name="sender">The <see cref="InterruptInput"/> object that raised the event.</param>
        /// <param name="value"><b>true</b> if the the value received from the interrupt is greater than zero; otherwise, <b>false</b>.</param>
        protected virtual void OnInterruptEvent(InterruptInput sender, bool value)
        {
            if (this.Interrupt == null)
            {
                return;
            }

            if (this.SynchronousUnsafeEventInvocation)
            {
                try
                {
                    this.Interrupt(sender, value);
                }
                catch
                {
                }
            }
            else
            {
                if (this.onInterrupt == null)
                {
                    this.onInterrupt = new InterruptEventHandler(this.OnInterruptEvent);
                }

                if (Program.CheckAndInvoke(this.Interrupt, this.onInterrupt, sender, value))
                {
                    this.Interrupt(sender, value);
                }
            }
        }
        /// <summary>
        /// Represents the delegate used for the <see cref="Interrupt"/> event.
        /// </summary>
        /// <param name="sender">The <see cref="InterruptInput"/> object that raised the event.</param>
        /// <param name="value"><b>true</b> if the the value received from the interrupt is greater than zero; otherwise, <b>false</b>.</param>
        public delegate void InterruptEventHandler(InterruptInput sender, bool value);

        /// <summary>
        /// Raised when the InterruptInput interface detects an interrupt.
        /// </summary>
        public event InterruptEventHandler Interrupt;
    }

}
